using System;
using System.Windows;
using Microsoft.Practices.CompositeUI;
using Microsoft.Practices.CompositeUI.SmartParts;
using Microsoft.Practices.ObjectBuilder;

namespace SCSFContrib.CompositeUI.WPF.BuilderStrategies
{
	/// <summary>
	/// A <see cref="BuilderStrategy"/> that walks the logical tree, looking for any child controls that should be added to the
	/// <see cref="WorkItem"/>.
	/// </summary>
	/// <remarks>
	/// <para>
	/// During build up, this <see cref="BuilderStrategy"/> ensures that any smart parts, workspaces, or smart part placeholders
	/// contained inside the item being built are added to the owning <see cref="WorkItem"/>. During tear down, any smart parts,
	/// workspaces, or smart part placeholders contained inside the item being unbuilt are removed from the owning
	/// <see cref="WorkItem"/>.
	/// </para>
	/// </remarks>
	public class FrameworkElementSmartPartStrategy : BuilderStrategy
	{
		private static readonly DependencyResolutionLocatorKey _workItemLocatorKey = new DependencyResolutionLocatorKey(typeof(WorkItem), null);

		/// <summary>
		/// Builds up <paramref name="existing"/>, ensuring that any relevant children are added to the current
		/// <see cref="WorkItem"/>.
		/// </summary>
		/// <param name="context">
		/// A context for the operation.
		/// </param>
		/// <param name="typeToBuild">
		/// The type of object that is being requested.
		/// </param>
		/// <param name="existing">
		/// The existing instance, if one was passed in, or if a previous strategy has already created the object.
		/// </param>
		/// <param name="idToBuild">
		/// The ID of the object that is being requested. May be <see langword="null"/> if the user requested an unnamed instance.
		/// </param>
		/// <returns>
		/// The built object.
		/// </returns>
		public override object BuildUp(IBuilderContext context, Type typeToBuild, object existing, string idToBuild)
		{
			FrameworkElement frameworkElement = existing as FrameworkElement;

			if (frameworkElement != null)
			{
				WorkItem workItem = context.Locator.Get<WorkItem>(_workItemLocatorKey);
				PopulateIfPlaceholder(workItem, frameworkElement);
				AddHierarchy(workItem, frameworkElement);
			}

			return base.BuildUp(context, typeToBuild, existing, idToBuild);
		}

		/// <summary>
		/// Tears down <paramref name="item"/>, ensuring any children are removed from the current <see cref="WorkItem"/>.
		/// </summary>
		/// <param name="context">
		/// A context for the operation.
		/// </param>
		/// <param name="item">
		/// The item to tear down.
		/// </param>
		/// <returns>
		/// The item that is being un-built.
		/// </returns>
		public override object TearDown(IBuilderContext context, object item)
		{
			FrameworkElement frameworkElement = item as FrameworkElement;

			if (frameworkElement != null)
			{
				WorkItem workItem = context.Locator.Get<WorkItem>(_workItemLocatorKey);
				RemoveHierarchy(workItem, frameworkElement);
			}

			return base.TearDown(context, item);
		}

		private void AddHierarchy(WorkItem workItem, FrameworkElement frameworkElement)
		{
			foreach (object child in LogicalTreeHelper.GetChildren(frameworkElement))
			{
				if (!AddToWorkItem(workItem, child))
				{
					FrameworkElement childFrameworkElement = child as FrameworkElement;

					if (childFrameworkElement != null)
					{
						AddHierarchy(workItem, childFrameworkElement);
					}
				}
			}
		}

		private bool AddToWorkItem(WorkItem workItem, object item)
		{
			if (BelongsInWorkItem(workItem, item))
			{
				FrameworkElement frameworkElement = item as FrameworkElement;

				if ((frameworkElement == null) || string.IsNullOrEmpty(frameworkElement.Name))
				{
					workItem.Items.Add(item);
				}
				else
				{
					workItem.Items.Add(item, frameworkElement.Name);
				}

				return true;
			}

			return false;
		}

		private delegate void RemoveHierarchyHandler(WorkItem workItem, DependencyObject dependencyObject);

		private void RemoveHierarchy(WorkItem workItem, DependencyObject dependencyObject)
		{
			//marshal to correct thread, for the (rare) case where there are multiple dispatcher threads
			if (!dependencyObject.Dispatcher.CheckAccess())
			{
				dependencyObject.Dispatcher.Invoke(System.Windows.Threading.DispatcherPriority.Send, new RemoveHierarchyHandler(RemoveHierarchy), workItem, dependencyObject);
			}
			else
			{
				RemoveHierarchyRecursive(workItem, dependencyObject);
			}
		}

		private void RemoveHierarchyRecursive(WorkItem workItem, DependencyObject dependencyObject)
		{
			foreach (object child in LogicalTreeHelper.GetChildren(dependencyObject))
			{
				workItem.Items.Remove(child);
				DependencyObject childDependencyObject = child as DependencyObject;

				if (childDependencyObject != null)
				{
					RemoveHierarchyRecursive(workItem, childDependencyObject);
				}
			}
		}

		private bool BelongsInWorkItem(WorkItem workItem, object item)
		{
			return !workItem.Items.ContainsObject(item) && (item is IWorkspace || item is ISmartPartPlaceholder || IsSmartPart(item));
		}

		private bool IsSmartPart(object item)
		{
			return (item.GetType().GetCustomAttributes(typeof(SmartPartAttribute), true).Length > 0);
		}

		private void PopulateIfPlaceholder(WorkItem workItem, FrameworkElement frameworkElement)
		{
			ISmartPartPlaceholder placeholder = frameworkElement as ISmartPartPlaceholder;

			if (placeholder != null)
			{
				object replacement = workItem.Items.Get(placeholder.SmartPartName);

				if (replacement != null)
				{
					placeholder.SmartPart = replacement;
				}
			}
		}
	}
}